package impl

import (
	"fmt"

	"hundsun.com/hsl/hschain/common/types"

	"hundsun.com/hsl/hschain/common/log/logging"
	"hundsun.com/hsl/hschain/common/message"
	pbcom "hundsun.com/hsl/hschain/protos/common"
	"hundsun.com/hsl/hschain/protos/ledger"
	storeLedger "hundsun.com/hsl/hschain/store/extend/ledger"
)

// Recover 从区块链恢复boss运行数据
func (chain *BlockChain) Recover() (int64, int64, error) {
	log := chain.log

	ldIns, err := chain.blockStore.GetLedgerService()
	if err != nil {
		log.Errorf("RecoverFromLedger NewLedger err %s", err.Error())
		return -1, -1, err
	}

	// 已经产生的块号
	producedNum := int64(-1)
	pBlockNum, err := ldIns.GetBlockNumber()
	if err != nil {
		log.Errorf("RecoverFromLedger GetBlockNumber failed %v", err)
		if err != types.ErrNotFound {
			return -1, -1, err
		}
	} else {
		chain.blockStore.height.Store(pBlockNum)
		producedNum = int64(pBlockNum)
	}

	// 已执行区块
	execdNum := int64(-1)
	eBlockNum, err := ldIns.GetMaxExecutedBlockNumber()
	if err != nil {
		log.Errorf("RecoverFromLedger GetMaxExecutedBlockNumber failed %v", err)
		if err != types.ErrNotFound {
			return -1, -1, err
		}
	} else {
		execdNum = int64(eBlockNum)
	}

	log.Infof("RecoverFromLedger producedNum is %d, execdNum is %d ", producedNum, execdNum)

	// 如果没有执行则说明不需要加载历史数据
	if execdNum < 0 {
		return producedNum, execdNum, nil
	}

	// 检查状态数据与账本数据的一致性
	info, err := chain.stateDBHandle.GetStateInfo()
	if info != nil && info.Number != uint64(execdNum) {
		eblk, err := ldIns.GetBlockExecResult(uint64(execdNum))
		if err != nil {
			panic(fmt.Sprintf("Recover GetBlockExecResult fail %v", err))
		}
		hash := chain.StateDataSetMemory(eblk)
		err = chain.StateDataCommit(hash)
		if err != nil {
			log.Warnf("Recover StateDataCommit fail %v", err)
		}
	}

	// recover state hash cache
	chain.LoadStateHashCache(execdNum, 100)

	// recover curBlockNum
	chain.curBlockNum = execdNum //BlockNumber从0开始
	chain.curRecvBlockNum = execdNum

	// recover tx history
	start := execdNum - int64(chain.txRecordNum) + 1
	if start < 0 {
		start = 0
	}
	end := execdNum
	step := int64(5)
	for i := start; i <= end; i += step {
		mid := i + step - 1
		if mid > end {
			mid = end
		}
		blocks, err := ldIns.GetBlockExecResults(uint64(i), uint64(mid))
		if err == nil {
			for _, block := range blocks {
				for _, tx := range block.TransactionExecResults {
					if tx == nil || tx.Receipt == nil {
						continue
					}
					chain.addHistroyTxs(block.GetBlockNumber(), tx.Receipt.TxHash)
				}
			}
		}
	}
	log.Infof("get GetBlockExecResults history tx range start blockNum is %d, end blockNum is %d", start, end)

	return producedNum, execdNum, nil
}

func (chain *BlockChain) storeFinishProc(eb *ledger.ExecutedBlock) {
	log := chain.log
	blockNum := eb.GetBlockExecResult().GetBlockNumber()

	// publish
	mqLog := log.WithField(logging.LogFieldMessageTopic, message.TopicExecutedBlock).WithField(logging.LogFieldBlockNumber, blockNum)
	mqLog.Infof("Begin publish block [%d] executed result message", blockNum)
	sysData, err := chain.pubsubMgr.PubSystem(eb, true)
	if err != nil {
		panic(fmt.Sprintf("pub systemData need no error, err is %v", err))
	}
	chain.pubsubMgr.PubNormal(eb, true)
	chain.sysparam.SetSysParam(int64(blockNum), sysData)
	mqLog.Infof("End publish block [%d] executed result message", blockNum)

	// del cache
	chain.delExecResultCache(blockNum)
}

// StoreLedgerData store ledger data
func (chain *BlockChain) StoreLedgerData(eb *ledger.ExecutedBlock) error {

	batch := chain.blockStore.ledgerDB.NewBatch(true)
	// 保存执行区块执行结果, 可用于在其它表存在异常情况下恢复数据，该数据可对其进行裁剪
	kv, _ := storeLedger.BlockExecResultKV(eb.GetBlockExecResult())
	if kv != nil {
		batch.Set(kv.Key, kv.Value)
	}

	// 保存交易
	if chain.EnableSaveExecutedTx {
		kvs, _ := chain.ExecutedTxKV(eb)
		if kvs != nil {
			for _, kv := range kvs.Kvs {
				batch.Set(kv.Key, kv.Value)
			}
		}
	}

	// 保存执行高度
	kv, _ = storeLedger.MaxExecutedBlockNumberKV(eb.GetBlockExecResult().GetBlockNumber())
	if kv != nil {
		batch.Set(kv.Key, kv.Value)
	}

	err := batch.Write()
	if err != nil {
		panic(fmt.Sprintf("StoreLedgerData batch write fail %v", err))
	}

	return nil
}

// ExecutedTxKV 保存已经执行交易
func (chain *BlockChain) ExecutedTxKV(eb *ledger.ExecutedBlock) (*pbcom.KeyValues, error) {
	if eb == nil {
		return nil, nil
	}
	executedTxs := chain.filterRealExecutedTxs([]*ledger.ExecutedBlock{eb})
	if len(executedTxs) > 0 {
		return storeLedger.AddTransactionsKV(executedTxs)
	}
	return nil, nil
}

func (chain *BlockChain) filterRealExecutedTxs(ebs []*ledger.ExecutedBlock) []*ledger.ExecutedTransaction {
	executedTxs := make([]*ledger.ExecutedTransaction, 0)
	if len(ebs) == 0 {
		return nil
	}
	// 过滤出已执行交易
	for _, eb := range ebs {
		txsMap := make(map[string]*ledger.Transaction)
		for _, tx := range eb.GetBlock().Transactions {
			txsMap[string(tx.Hash())] = tx
		}
		transExecRst := eb.GetBlockExecResult().TransactionExecResults
		for _, trsExec := range transExecRst {
			recpt := trsExec.GetReceipt()
			if recpt == nil || recpt.TxStatus == ledger.TransactionReceipt_DUPLICATED_TX {
				continue
			}
			if tx, ok := txsMap[string(recpt.TxHash)]; ok {
				exectx := &ledger.ExecutedTransaction{
					Tx:      tx,
					Receipt: recpt,
				}
				executedTxs = append(executedTxs, exectx)
			}
		}
	}
	return executedTxs
}
